QuickTime 3 Reference

Previous | Chapter Top | Chapter Contents | Next

Defining a Key Frame Sample

In order to create a sprite track in a QuickTime movie, you must first create the movie ifself, a track to contain the sprites, and the track's media. Then, you define a key frame sample. A key frame sample defines the number of sprites, their initial property values, and the shared image data used by the sprites in the key frame sample and in all override samples that follow the key frame sample. The sample code discussed in this section creates a single key frame sample; however, a sprite track may contain multiple key frame samples, each with its own override samples.

Creating the Movie, Sprite Track, and Media

Listing 1 shows a code fragment from the sample code function CreateSampleSpriteMovie . This function creates a new movie file and calls another sample code function, AddSpriteTrackToMovie , which is responsible for creating a sprite track and adding it to the movie.

Listing 1 Creating a sprite track movie

// global constants
#define kSpriteTrackWidth 640
#define kSpriteTrackHeight 480


Movie       theMovie = nil;

// ...
// create a movie file
// ...

// add the sprite track to the movie
FailOSErr (AddSpriteTrackToMovie (theMovie, kSpriteTrackWidth,
    kSpriteTrackHeight, true));

The following code fragment from AddSpriteTrackToMovie ( Listing 2 ) creates a new track and media, and calls BeginMediaEdits to prepare to add samples to the track's media.

Listing 2 Creating a track and media

// global constants
#define kSpriteMediaTimeScale 600


newTrack = NewMovieTrack (theMovie, ((long)trackWidth << 16),
    ((long)trackHeight << 16), 0);
newMedia = NewTrackMedia (newTrack, SpriteMediaType,
    kSpriteMediaTimeScale, nil, 0);

FailOSErr (BeginMediaEdits (newMedia));

Adding Images to the Key Frame Sample

Listing 3 shows the first part of the AddSpriteTrackToMovie function. The first task the function performs is to create a QT atom container to hold the key frame sample and add all of the images to be used for sprites to the key frame sample. For each image, this function calls the sample code function AddPICTImageToKeyFrameSample to compress the PICT image and then adds the compressed image to the key frame sample. The last parameter passed to the AddPICTImageToKeyFrameSample is the index of the image.

Listing 3 Adding images to the key frame sample

// global constants
#define kIconPictID 127
#define kWorldPictID 128
#define kBackgroundPictID 158
#define kFirstSpaceShipPictID (kBackgroundPictID + 1)
#define kNumSpaceShipImages 24


RGBColor keyColor;

keyColor.red = keyColor.green = keyColor.blue = 0xFFFF;// white

// create an empty key frame sample
FailOSErr (QTNewAtomContainer(&sample));

// add images to the key frame sample
err = AddPICTImageToKeyFrameSample (sample, kIconPictID,
    &keyColor, 1);
err = AddPICTImageToKeyFrameSample (sample, kWorldPictID,
    &keyColor, 2);
err = AddPICTImageToKeyFrameSample (sample, kBackgroundPictID,
    &keyColor, 3);
for ( i = 1; i <= kNumSpaceShipImages; i++ )
    err = AddPICTImageToKeyFrameSample (sample,
        kFirstSpaceShipPictID + i - 1, &keyColor, i + 3);

The AddPICTImageToKeyFrameSample function ( Listing 4 ) calls another sample code function, MakePictTransparent , which strips any surrounding background color from a PICT image. MakePictTransparent does this by using the animation compressor to recompress the PICT image using a key color.

AddPICTImageToKeyFrameSample then calls the sample code function ExtractCompressData , which extracts the compressed image data from the PICT image. Finally, AddPICTImageToKeyFrameSample calls the sample code function AddCompressedImageToKeyFrameSample , which is responsible for preparing the compressed image data and adding it to the key frame sample.

Listing 4 The AddPICTImageToKeyFrameSample function

OSErr AddPICTImageToKeyFrameSample (QTAtomContainer keySample,
    short pictID, RGBColor *keyColor, short id)
{
    OSErr                   err = noErr;
    PicHandle               picture;
    Handle                  compressedPicture;
    ImageDescriptionHandle  idh;
    
    // get picture from resource
    picture = (PicHandle) GetPicture (pictID);
    DetachResource ((Handle)picture);
    
    // make the PICT "transparent"
    MakePictTransparent (picture, keyColor);
    // extract the compressed image data from the PICT
    ExtractCompressData (picture, &compressedPicture, &idh);

    // prepare the compressed image and add it to the key frame sample
    HLock (compressedPicture);
    AddCompressedImageToKeyFrameSample (keySample, idh,
        GetHandleSize (compressedPicture), *compressedPicture, id);
    
bail:
    if (picture)
        KillPicture (picture);
    if (compressedPicture)
        DisposeHandle( compressedPicture );
    if (idh)
        DisposeHandle ((Handle)idh);
    return err;
}

The AddCompressedImageToKeyFrameSample function ( Listing 5 ) converts the compressed image to the appropriate format to be used by a sprite by appending the compressed image to an image description handle. Then, the function adds the appropriate atoms to represent the sprite image to the key frame sample:

Listing 5 The AddCompressedImageToKeyFrameSample function

OSErr AddCompressedImageToKeyFrameSample (QTAtomContainer keySample,
    ImageDescriptionHandle idh, long dataSize, Ptr compressedDataPtr,
    QTAtomID imageID)
{
    OSErr   err = noErr;
    Handle  imageData;
    QTAtom  defaultsAtom, imagesContainerAtom, imageAtom;
    
    // append compressed picture data to imageDescription to
    // obtain sprite image data
    FailMemErr (imageData = NewHandle(0));
    FailMemErr (HandAndHand((Handle)idh, imageData));
    FailMemErr (PtrAndHand (compressedDataPtr, imageData, dataSize));
    
    // if no kSpriteSharedDataAtomType atom in key sample, add one
    if ((defaultsAtom = QTFindChildByIndex (keySample, 0,
        kSpriteSharedDataAtomType, 1, nil)) == 0)
        FailOSErr (QTInsertChild (keySample, 0,
            kSpriteSharedDataAtomType, 1, 0, 0, nil, &defaultsAtom));
        
    // if no kSpriteImagesContainerAtomType in key sample, add one
    if ((imagesContainerAtom = QTFindChildByIndex (keySample,
        defaultsAtom, kSpriteImagesContainerAtomType, 1, nil)) == 0)
        FailOSErr (QTInsertChild (keySample, defaultsAtom,
            kSpriteImagesContainerAtomType, 1, 0, 0, nil,
            &imagesContainerAtom));

    // add the image and image data atoms to the key sample
    FailOSErr (QTInsertChild(keySample, imagesContainerAtom,
        kSpriteImageAtomType, imageID, 0, 0, nil, &imageAtom));
    HLock (imageData);
    FailOSErr (QTInsertChild (keySample, imageAtom,
        kSpriteImageDataAtomType, 1, 0, GetHandleSize(imageData),
        *imageData, nil));
    
bail:
    if (imageData)
        DisposeHandle (imageData);
    return err;
}

Adding Sprites to the Key Frame Sample

The AddSpriteTrackToMovie function adds the sprites with their initial property values to the key frame sample, as shown in Listing 6 . If the withBackgroundPicture parameter is true, the function adds a background sprite. The function initializes the background sprite's properties, including setting the layer property to kBackgroundSpriteLayerNum to indicate that the sprite is a background sprite. The function calls SetSpriteData ( Listing 7 ), which adds the appropriate property atoms to the spriteData atom container. Then, AddSpriteTrackToMovie calls AddSpriteToSample ( Listing 8 ) to add the atoms in the spriteData atom container to the key frame sample atom container.

AddSpriteTrackToMovie adds the other sprites to the key frame sample and then calls AddSpriteSampleToMedia ( Listing 9 ) to add the key frame sample to the media.

Listing 6 Adding sprites to the key frame

// global constants
#define kBackgroundImageIndex 3
#define kFirstSpaceShipImageIndex 4
#define kSpriteMediaFrameDuration 8


FailOSErr (QTNewAtomContainer (&spriteData));

if (withBackgroundPicture)
{
    // add background sprite
    location.h = 0;
    location.v = 0;
    visible = true;
    layer = kBackgroundSpriteLayerNum;
    imageIndex = kBackgroundImageIndex;
    SetSpriteData (spriteData, &location, &visible, &layer, &imageIndex);
    err = AddSpriteToSample (sample, spriteData, 1);
}

// add space ship sprite
location.h = 0;
location.v = 60;
visible = true;
layer = -1;
imageIndex = kFirstSpaceShipImageIndex;
SetSpriteData (spriteData, &location, &visible, &layer, &imageIndex);
err = AddSpriteToSample (sample, spriteData, 2);

// ...
// add other sprites
// ...

err = AddSpriteSampleToMedia (newMedia, sample,
    kSpriteMediaFrameDuration, true);

For each new property value that is passed into it as a parameter, the SetSpriteData function ( Listing 7 ) calls QTFindChildByIndex to find the appropriate property atom. If the property atom already exists in the QT atom container, SetSpriteData calls QTSetAtomData to update the property's value. If the property atom does not exist in the container, SetSpriteData calls QTInsertChild to insert a new property atom.

Listing 7 The SetSpriteData function

OSErr SetSpriteData (QTAtomContainer sprite, Point *location,
    short *visible, short *layer, short *imageIndex)
{
    OSErr   err = noErr;
    QTAtom  propertyAtom;
    
    if (location) {
        MatrixRecordmatrix;
        
        // set up the value for the matrix property
        SetIdentityMatrix (&matrix);
        matrix.matrix[2][0] = ((long)location->h << 16);
        matrix.matrix[2][1] = ((long)location->v << 16);
        
        // if no matrix atom is in the container, insert a new one
        if ((propertyAtom = QTFindChildByIndex (sprite, 0,
            kSpritePropertyMatrix, 1, nil)) == 0)
            FailOSErr (QTInsertChild (sprite, 0, kSpritePropertyMatrix,
                1, 0, sizeof(MatrixRecord), &matrix, nil))
        // otherwise, replace the atom's data
        else
            FailOSErr (QTSetAtomData (sprite, propertyAtom,
                sizeof(MatrixRecord), &matrix));
    }

    // ...
    // handle other properties in a similar fashion
    // ...

    return err;
}

The AddSpriteToSample function ( Listing 8 ) checks to see whether a sprite has already been added to a sample. If not, the function calls QTInsertChild to create a new sprite atom in the atom container that represents the sample. Then, AddSpriteToSample calls QTInsertChildren to insert the atoms in the sprite atom container as children of the newly created atom in the sample container.

Listing 8 The AddSpriteToSample function

OSErr AddSpriteToSample (QTAtomContainer theSample,
    QTAtomContainer theSprite, short spriteID)
{
    OSErr err = noErr;
    QTAtom newSpriteAtom;
    
    FailIf (QTFindChildByID (theSample, 0, kSpriteAtomType, spriteID,
        nil), paramErr);
    
    FailOSErr (QTInsertChild (theSample, 0, kSpriteAtomType, spriteID,
        0, 0, nil, &newSpriteAtom)); // index of zero means append
    FailOSErr (QTInsertChildren (theSample, newSpriteAtom, theSprite));

bail:
    return err;
}

The AddSpriteSampleToMedia function, shown in Listing 9 , calls AddMediaSample to add either a key frame sample or an override sample to the sprite media.

Listing 9 The AddSpriteSampleToMedia function

OSErr AddSpriteSampleToMedia (Media theMedia, QTAtomContainer sample,
    TimeValue duration, Boolean isKeyFrame)
{
    OSErr err = noErr;
    SampleDescriptionHandle sampleDesc = nil;
    
    FailMemErr (sampleDesc = (SampleDescriptionHandle) NewHandleClear(
        sizeof(SampleDescription)));
    
    FailOSErr (AddMediaSample (theMedia, (Handle) sample, 0,
        GetHandleSize(sample), duration, sampleDesc, 1,
        isKeyFrame ? 0 : mediaSampleNotSync, nil));

bail:
    if (sampleDesc)
        DisposeHandle ((Handle)sampleDesc);

    return err;
}

© 1997 Apple Computer, Inc.

Previous | Chapter Top | Chapter Contents | Next